﻿using System; 
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Collections.Generic;

using SBPweb.Data;

#if SQL2005
namespace SBPweb.Data.SQL2005
{
	/// <summary>
    /// A generic repository base class that uses various
    /// factory classes to get and retrieve specific domain
    /// objects.
    /// </summary>
	public class SqlRepository<TDomainObject> : Repository<TDomainObject>
	{

		#region Constructor logic

		/// <summary>
		/// Initializes a new SqlRepository instance
		/// </summary>
		public SqlRepository()
			:base()
		{
		}

		/// <summary>
		/// Initializes a new SqlRepository instance
		/// </summary>
		/// <param name="connectionString">Connection string to sql database</param>
		public SqlRepository(string connectionString)
			:base(connectionString)
        {
			DatabaseConnectionString = connectionString;
			provider = SqlConnectionProvider.SessionConnectionProvider;
		}

		/// <summary>
		/// Initializes a new SqlRepository instance
		/// </summary>
		/// <param name="connectionString">Connection string to sql database</param>
		/// <param name="connectionProvider">SqlConnectionProvider instance</param>
		public SqlRepository(string connectionString, SqlConnectionProvider connectionProvider)
		{
			DatabaseConnectionString = connectionString;
			provider = connectionProvider;
		}

		#endregion

		#region Proteted members

		/// <summary>
		/// SqlConnection connection
		/// </summary>
		protected SqlConnection connection;
		/// <summary>
		/// SqlConnectionProvider provider
		/// </summary>
		protected SqlConnectionProvider provider;

		#endregion

		#region Public properties

		/// <summary>
		/// Gets or sets connected SqlDatabase
		/// </summary>
		public virtual SqlDatabase DB
		{
			get
			{
				return db as SqlDatabase;
			}
			set
			{
				db = value;
			}
		}

		/// <summary>
		/// Gets or sets the current SqlTransaction
		/// </summary>
		public virtual SqlTransaction Transaction
		{
			get
			{
				return transaction as SqlTransaction;
			}
			set
			{
				transaction = value;
			}
		}

		/// <summary>
		/// Gets or sets the DatabaseConnectionString
		/// </summary>
		public virtual string DatabaseConnectionString
		{
			get
			{
				return databaseConnectionString;
			}
			set
			{
				databaseConnectionString = value;
			}
		}

		/// <summary>
		/// Gets or sets the current SqlConnection
		/// </summary>
		public virtual SqlConnection Connection
		{
			get
			{
				return connection;
			}
			set
			{
				connection = value;
			}
		}

		/// <summary>
		/// Gets or sets the used SqlConnectionProvider
		/// </summary>
		public virtual SqlConnectionProvider Provider
		{
			get
			{
				return provider;
			}
			set
			{
				provider = value;
			}
		}

		#endregion

		#region Public transaction handling methods

		/// <summary>
		/// Opens the connection to sql database
		/// </summary>
		public void OpenConnection()
		{
			DB = Provider.GetSqlDatabase(DatabaseConnectionString);
			Connection = Provider.GetSqlConnection(DatabaseConnectionString);
			Transaction = null;
		}

		/// <summary>
		/// Starts transaction
		/// </summary>
		public override void BeginTransaction()
		{
			if (Connection == null)
			{
				OpenConnection();
			}
			Transaction = Provider.GetSqlTransaction(DatabaseConnectionString);
		}
		
		/// <summary>
		/// Rollbacks transaction
		/// </summary>
		public virtual void Rollback()
		{
			if (Transaction != null && Transaction.Connection != null)
			{
				if (Transaction.Connection.State == ConnectionState.Open)
				{
					Transaction.Rollback();
				}
				else
				{
					throw new Exception(string.Format("{0 - Cannot rollback transaction because connection state is {1}!", Transaction.ToString(), Transaction.Connection.State.ToString()));
				}
			}
			else
			{
				throw new Exception(string.Format("{0 - Cannot rollback transaction because connection is closed!", Transaction.ToString()));
			}
		}

		/// <summary>
		/// Commits transaction
		/// </summary>
		public virtual void Commit()
		{
			if (Transaction != null && Transaction.Connection != null)
			{
				if (Transaction.Connection.State == ConnectionState.Open)
				{
					Transaction.Commit();
				}
				else
				{
					throw new Exception(string.Format("{0 - Cannot commit transaction because connection state is {1}!", Transaction.ToString(), Transaction.Connection.State.ToString()));
				}
			}
			else
			{
				throw new Exception(string.Format("{0 - Cannot commit transaction because connection is closed!", Transaction.ToString()));
			}
		}

		#endregion

		#region Public CRUD methods

		/// <summary>
		/// Returns DomainObjects that met with the given identity
		/// </summary>
		/// <typeparam name="TIdentity">Type of the identity</typeparam>
		/// <param name="selectionFactory">Factory that creates command</param>
		/// <param name="domainObjectFactory">Factory that creates the DomainObject</param>
		/// <param name="identity">Identity object</param>
		/// <returns>List of DomainObjects</returns>
        public List<TDomainObject> Find<TIdentity>(ISelectionFactory<TIdentity> selectionFactory, SBPweb.Data.Generic.IDomainObjectFactory<TDomainObject> domainObjectFactory, TIdentity identity)
        {
            List<TDomainObject> results = new List<TDomainObject>();

            using (SqlCommand command = selectionFactory.ConstructSelectCommand(DB, identity))
            {
                
                SBPweb.Common.Diagnostics.MeasuredInterval mi = new SBPweb.Common.Diagnostics.MeasuredInterval();

                using (IDataReader rdr = DB.ExecuteReader(command))
                {
                    while (rdr.Read())
                    {
                        results.Add(domainObjectFactory.Construct(rdr));
                    }
                }

                LogManager.WriteVerbose(this, string.Format("Execution of {0} [{1}] took {2}.", command, command.CommandText, mi.Stop()), "SBPweb.Data.Repository");

            }
            return results;
        }

		/// <summary>
		/// Returns the first DomainObject that met with the given identity
		/// </summary>
		/// <typeparam name="TIdentity">Type of the identity</typeparam>
		/// <param name="selectionFactory">Factory that creates command</param>
		/// <param name="domainObjectFactory">Factory that creates the DomainObject</param>
		/// <param name="identity">Identity object</param>
		/// <returns>DomainObject</returns>
		public TDomainObject FindOne<TIdentity>(ISelectionFactory<TIdentity> selectionFactory, SBPweb.Data.Generic.IDomainObjectFactory<TDomainObject> domainObjectFactory, TIdentity identity)
        {
            TDomainObject result = default(TDomainObject);
			using (SqlCommand command = selectionFactory.ConstructSelectCommand(DB, identity))
            {

                SBPweb.Common.Diagnostics.MeasuredInterval mi = new SBPweb.Common.Diagnostics.MeasuredInterval();

                using (IDataReader rdr = DB.ExecuteReader(command))
                {
                    if(rdr.Read())
                    {
                        result = domainObjectFactory.Construct(rdr);
                    }
                }

                LogManager.WriteVerbose(this, string.Format("Execution of {0} [{1}] took {2}.", command, command.CommandText, mi.Stop()), "SBPweb.Data.Repository");

            }
            return result;
        }

		/// <summary>
		/// Inserts a new DomainObject into the database
		/// </summary>
		/// <param name="insertFactory">Factory that creates the insert command</param>
		/// <param name="domainObj">DomainObject to insert</param>
        public void Add(IInsertFactory<TDomainObject> insertFactory, TDomainObject domainObj )
        {
            using(SqlCommand command = insertFactory.ConstructInsertCommand(DB, domainObj))
            {
                SBPweb.Common.Diagnostics.MeasuredInterval mi = new SBPweb.Common.Diagnostics.MeasuredInterval();
				DoExecuteNonQuery(command);
				insertFactory.SetNewID(DB, command, domainObj);
                LogManager.WriteVerbose(this, string.Format("Execution of {0} [{1}] took {2}.", command, command.CommandText, mi.Stop()), "SBPweb.Data.Repository");
            }
        }

		/// <summary>
		/// Removes a DomainObject from the database
		/// </summary>
		/// <typeparam name="TIdentityObject">Type of identity object</typeparam>
		/// <param name="deleteFactory">Factory that creates the delete command</param>
		/// <param name="identityObj">Identity object</param>
        public void Remove<TIdentityObject>(IDeleteFactory<TIdentityObject> deleteFactory, TIdentityObject identityObj )
        {
			using (SqlCommand command = deleteFactory.ConstructDeleteCommand(DB, identityObj))
            {
                SBPweb.Common.Diagnostics.MeasuredInterval mi = new SBPweb.Common.Diagnostics.MeasuredInterval();
				DoExecuteNonQuery(command);
                LogManager.WriteVerbose(this, string.Format("Execution of {0} [{1}] took {2}.", command, command.CommandText, mi.Stop()), "SBPweb.Data.Repository");
			}
        }

		/// <summary>
		/// Updates a DomainObject in the database
		/// </summary>
		/// <param name="updateFactory">Factory that creates tha update object</param>
		/// <param name="domainObj">DomainObject to update</param>
		public void Update(IUpdateFactory<TDomainObject> updateFactory, TDomainObject domainObj)
		{
			using (SqlCommand command = updateFactory.ConstructUpdateCommand(DB, domainObj))
			{
                SBPweb.Common.Diagnostics.MeasuredInterval mi = new SBPweb.Common.Diagnostics.MeasuredInterval();
				DoExecuteNonQuery(command);
                LogManager.WriteVerbose(this, string.Format("Execution of {0} [{1}] took {2}.", command, command.CommandText, mi.Stop()), "SBPweb.Data.Repository");
			}
		}

		/// <summary>
		/// This method executes a non query command on database
		/// </summary>
		/// <param name="command">Command to execute</param>
		public void DoExecuteNonQuery(SqlCommand command)
		{
			if (Transaction == null)
			{
				DB.ExecuteNonQuery(command);
			}
			else
			{
				DB.ExecuteNonQuery(command, Transaction);
			}
		}

		#endregion

	}
}
#endif